其他
干货 | 携程火车票Flutter最佳实践
作者简介
///多个NotifierProvider的时候
return MultiProvider(providers: [
ChangeNotifierProvider(create: (context) => dataViewModel(mCommonAdvancedFilterRoot,query)),
ChangeNotifierProvider(create: (context) => UserPreferentialViewModel(query)),
ChangeNotifierProvider(create: (context) => UserPromotionViewModel())
///需要调用共享数据的子Widget
], child: ListResearchPageful(query));
///在StatefulWidget中的build()方法中获取ViewModel
class ListResearchPageState extends TripState<ListResearchPageful> {
@override
Widget build(BuildContext context) {
///使用Provider包装以后,可以在widget的任一一个子widget获取共享数据并操作数据,在这里就是可以在HotelListView方法下的唯一位置获取ViewModel
var listViewModel = Provider.of<ListDataViewModel>(context);
var userPromotionViewModel = Provider.of<UserPromotionViewModel>(context);
return MediaQuery(
child: QueryListPage(widget.query,
ListDataViewModel, userPromotionViewModel));
}
}
///借用Builder组件进行获取ViewModel
@override
Widget build(BuildContext context) {
///使用Provider包装以后,可以在widget的任一一个子widget获取共享数据并操作数据,在这里就是可以在ListView方法下的唯一位置获取ListDataViewModel
var userPromotionViewModel = Provider.of<UserPromotionViewModel>(context);
return MediaQuery(
child: Builder(builder: (context) {
var listDataViewModel = Provider.of<ListDataViewModel>(context);
return queryListPage(
widget.query, listDataViewModel, userPromotionViewModel);
},));
}
//领券监听
///此处可以直接使用viewModel调用viewmodel中的方法
Event.addEventListener(
"UPDATE_QUERY_RESULT_LIST",(eventName, eventData) {
if (isOnPause) {
listViewModel.isNeedRefresh = true;
listViewModel.refreshListData(listViewModel.query);
} else {
listViewModel.refreshListData(listViewModel.query);
}
});
纯 Flutter 项目构建 Profile 模式
Flutter 与 Native 混合项目构建 Profile 模式
// 进入flutter项目,执行build-release,并指定输出目录 tripflutter
build-release -o /projects/ctrip_flutter/release -i info
### 开启Profile模式
TRIP_FLUTTER_PROFILE=true
### 设置profile模式下js使用的产物目录(过程1构建的 ./profile 目录)
TRIP_FLUTTER_LOCAL_OUTPUTS_PATH=/projects/ctrip_flutter/profile
获取FPS数值来衡量页面性能,方便对比Flutter、Native页面帧率;
直观统计页面在各个机型上面的表现;
定位页面的具体哪个模块有问题;
检查和分析应用程序的UI布局和状态。
诊断应用的UI 性能问题。
检测和分析应用程序的CPU使用情况。
分析应用程序的网络使用情况。
Flutter或Dart应用程序的源代码级调试。
调试Flutter或Dart应用程序的内存使用情况和分析内存问题。
查看运行的Flutter或Dart应用程序的一般日志和诊断信息。
///Bad code 不推荐使用children 构建List
ListView(children: getItems(mList))
List<Widget> getItems(List<FilterNode> mList){
List<Widget> items=new List<Widget>();
if(null!=mList){
for(Node node in mList){
items.add(Text("不推荐写法"));
}
}
return items;
}
///推荐写法
ListView.builder(
// physics: NeverScrollableScrollPhysics(),
//shrinkWrap: true,
itemCount:mList.length,
itemBuilder: (BuildContext context, int index) {
return Text("推荐使用ListView.builder()");
})
)
尽量避免在滑动监听中触发setStat()刷新视图。
scrollController.addListener(() {
if (scrollController.offset > scrollHeight && titleAlpha != 255) {
setState(() {
titleAlpha = 255;
});
}
if (scrollController.offset <= 0 && titleAlpha != 0) {
setState(() {
titleAlpha = 0;
});
}
if (scrollController.offset > 0 && scrollController.offset < scrollHeight) {
setState(() {
titleAlpha = scrollController.offset * 255 ~/ scrollHeight;
});
}
});
尽量将setStat()放在放置于视图树的低层级,好处是build时影响范围极小,简称局部刷新。
Widget build(BuildContext context) {
return Text(timeRemaining,
style: TextStyle(
color: HotelColors.hotel_list_reduction_sale_color,
fontSize: 10,
fontWeight: FontUtil.mediumWeight));
}
///存放界面所有的widgets,用以缓存
List<Widget> widgets = new List<Widget>();
///因为头部布局是静态的不刷新,使用变量控制是否复用以前的widgets
var refreshPage = true;
///获取界面布局所有的widgets
List<Widget> getPageWidgets(ScriptDataEntity data) {
if(widgets.isNotEmpty && !refreshPage) {
return widgets;
}
}
///对每一页加载的数据进行做图片预加载
(hotelListViewModel.currentPageHotels ?? []).forEach((element) {
var logo = element?.logo ?? "";
if (StringUtil.isNotEmpty(logo)) {
precacheImage(NetworkImage(logo), context);
}
});
///请求列表数据数据
void loadListData(HotelQuery query) {
///在首页提前获取列表页的数据并缓存到本地,当用户进入列表页时可以直接展示数据
if (resultModel != null) {
///判断是否需要再次请求数据
_dealWithResult(resultModel);
return;
} else if (isPreloading) {
///通过桥方法获取首页已经缓存的数据 HotelBridge.getListCache<Map>({'queryModel':query.toJson()})
.then((resp) {
final newResultModel =
QueryResultModel.fromJson(resp);
///有缓存数据直接处理使用
_dealWithResult(newResultModel);
}).catchError((error) {
///没有数据采取请求列表页的数据
getHotelList();
});
}
}
可以将需要自适应高度的Widget使用ConstrainedBox进行包裹,并设置最低高度;
将图片作为Container的背景图片,使用DecorationImage进行修饰当前的Container;
将图片的填充方式设置为BoxFit.Cover或者fillHeight即可;
///从服务器端获取当前活动终止时间,当服务器返回以后,会通知刷新这里
///如果用户在数据返回之前销毁该界面,等数据回来以后刷新界面就会报错
final endTime = roomDetailItemEntity?.tonightEndTime ?? '';
int endTimeOfNum = 0;
if (endTime.isNotEmpty) {
try {
endTimeOfNum = int.parse(endTime) ?? 0;
if(endTimeOfNum - Util.currentTimeMillis() > 0) {
this.setState(() {
_showCountDown = true;
});
}
} catch (e) {}
}
final endTime = roomDetailItemEntity?.tonightEndTime ?? '';
int endTimeOfNum = 0;
if (endTime.isNotEmpty) {
try {
endTimeOfNum = int.parse(endTime) ?? 0;
if(endTimeOfNum - Util.currentTimeMillis() > 0) {
if(mounted) {
this.setState(() {
_showCountDown = true;
});}}} catch (e) {}
}
Widget hotelListDesContent(BuildContext context) {
return Container(
///此处想实现左边是图片,右边是相关信息的布局,如果MediaQuery.on(context).size.width获取为0时,就会报出异常
width: MediaQuery.of(context).size.width - Dimens.image_width80,
///右边内容
child: Stack(children: [
Container(child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
hotelListDesName(),
englishName(),
hotelListRemarkContent(),],),),
///左边图片
Positioned(child: fullRoomItem()),
],
));
}
Widget hotelListDesContent(BuildContext context) {
return Expanded(
flex: 1,child: Stack(
children: [Container(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
hotelListDesName(),
englishName(),
hotelListRemarkContent(),],),),
Positioned(child: fullRoomItem()),
],
));
}
HotelServices.getTyHotelRoomPrice(params, ApiCallBack(onSuccess: (Object obj) {
this.roomPriceEntity = HotelRoomPriceEntity.fromJson(obj);
this.resultCode = 1;
///如果在数据返回是,用户已经关闭当前界面,此处通知刷新界面会导致crash
notifyListeners();
}, onError: (int code, String message) {}
notifyListeners()
}));
import 'package:flutter/cupertino.dart';
/// ViewModel基类
class HotelViewModel extends ChangeNotifier{
bool _disposed = false;
@override
void dispose() {
_disposed = true;
super.dispose();
}
void hotelNotifyListeners() {
if(!_disposed){
notifyListeners();
}
}
}
///母房型名称, 当前我们Text最大显示两行,当大于两行是,出现...,可是此时第二个组件无处显示,当用户点击就会crash
Row(children: <Widget>[
Expanded(child: Text.rich(TextSpan(
children: [TextSpan(
text: itemRoomEntity.baseName ??""),
WidgetSpan(
child: Container(
padding: EdgeInsets.only(bottom: Dimens.gap_dp3),
child: Icon(HotelIcons.show_more),
),
),
]), maxLines: 2, overflow: TextOverflow.ellipsis),
),
], crossAxisAlignment: CrossAxisAlignment.center,),
///母房型名称
Row( mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Flexible(child: Text((childCount > 1)?itemRoomEntity.baseName ?? "":"",
maxLines: 2,
overflow: TextOverflow.ellipsis,),),
Container(child: Icon(childCount ==1?HotelIcons.show_more:null),
margin: EdgeInsets.only(top: Dimens.gap_dp2),),
], crossAxisAlignment: CrossAxisAlignment.center,)
“携程技术”公众号
分享,交流,成长